| ## Charlie Calvert's C++ Builder Unleashed ## - 24 - ## Creating Non-Visual Components ### Overview In this chapter, you will learn how to build a non-visual component. In particular, I will emphasize the following topics: Descending directly from `TComponent` Using and designing non-visual components Using `FindFirst` and `FindNext` to iterate through the files in directories Creating your own stacks Pushing and popping items off a stack In particular, you will look at one reusable non-visual component called `TFindDirs`, which is used in a program that will iterate through a series of directories and will archive the names of the files found there into a database. You could, for example, use the program to iterate over all the files on several zip discs of CD-ROMs. You would then have one database that could be searched when you're looking for files that might be located on any of a number of different discs. ### Why Create Non-Visual Components? The program shown in this chapter uses the `TFindDirs` component to illustrate the concept of component reuse. Write the `TFindDirs` component once, and you can reuse it in multiple programs. The big bonus here is that `TFindDirs` is a component and thereby makes a relatively complex task simple enough that you can perform it by just dropping an object onto a form and plugging it into your program. This short chapter is presented primarily to promote the idea of turning objects that you might not usually think of as components into components. You can easily see why an edit control makes a good component, but the fact that an object used to iterate through directories would make a good component is less obvious. In fact, I first turned this object into a component on a whim. Once I had it around, however, I found that I could plug it into all kinds of applications to aid with one task or another. Here are some of my applications that use this component: A program for iterating through directories to delete files that I no longer need. A program for "touching" all the files in a series of directories so that they have the same date. A file backup utility for comparing two directory structures to be sure they are identical. This same program will also produce scripts that copy files from one directory structure to the other if a mismatch occurs. My point here is that writing these utilities became easy after I had the `TFindDirs` component in place. The core functionality for each of these programs was easy to implement because it was based on a component. As you will see, in just a few seconds you can create a program that uses the component. After you have that much functionality in place, you can easily add more features. ### When Should Non-Visual Objects Become Components? Whether you should turn a particular object into a component is not always clear. For example, the `TStringList` object has no related component and cannot be manipulated through visual tools. The question then becomes "Why have I taken the `TFindDirs` object and placed it on the Component Palette?" As you'll discover, the advantages of placing `TFindDirs` on the Component Palette are two-fold: You might need to tweak several options before you use this object. In particular, you need to decide whether you want to have the lists of directories and files that you find saved to memory in a `TStringList`. Letting the programmer decide these matters by clicking a property can go a long way toward presenting a clean, easy-to-use interface for an object. The `TFindDirs` object has two features that can be accessed through the `Events` page. Specifically, custom event handlers can be notified every time a new file or directory has been found. However, constructing an event handler manually can be confusing, particularly if you don't know which parameters will be passed to the functions involved. If you place a component on the Component Palette, you do not need to guess about how to handle an event. All it takes is a quick click on the `Events` page, and the event handler is created for you automatically! Creating a component also has the enormous advantage of forcing, or at least encouraging, programmers to design a simple interface for an object. After I have placed an object on the Component Palette, I always want to ensure that the user can hook it into his or her program in only a few short seconds. I am therefore strongly inclined to create a simple, easy-to-use interface. If I don't place the component on the Component Palette, then I find it easier to slip by with a complex interface that takes many lines of code for myself and others to utilize. To my mind, good components are not only bug free, but also very easy to use. ### The SearchDirs Program In this section, you will find the SearchDirs program, which can be used to iterate through the subdirectories on a hard drive looking for files with a particular name or file mask. For example, you could look for `*.cpp` or `m*.cpp` or `ole2.hpp`. This program will put the names of all the files that match the mask into a database, so you can search for the files later. The SearchDirs program depends on the `TFindDirs` component, which ships with this book. To use this component, you must first load it onto the Component Palette, using the techniques described in the preceding few chapters. In general, all you have to do is choose Component | Install and then click the Add button. Browse the `Utils` subdirectory that ships with this book. There you will find the `FindDirs2.cpp` unit. Add it to `CMPLIB32.CCL`, and you are ready to build the SearchDirs program. As usual, you might want to run the BuildObjs project once before installing the component. The source for this program is shown in Listings 24.1 through 24.8. A screen shot of the program is shown in Figure 24.1. You need to make sure the `CUnleashed` alias is in place before running the program. This is a standard Paradox alias that points at the data directory off the root directory where the files from the CD that accompany this book are installed. See both the text right after the listings and also the readme file for more information about the alias. FIGURE 24.1. A screen shot of the main form of the SearchDirs program. The point here is that `TFindDirs`, like `TTable` and `TQuery`, is a non-visual component. `TFindDirs` completes your introduction to the basic component types by showing you how to build nonvisual components. You already know how to build visual components. After you understand how to build non-visual components, most of the power of BCB will be open to you. The `TFindDirs` component is also important because it shows you how to create custom event handlers. Listing 24.1. The header for the main form for the SearchDirs program.
    ///////////////////////////////////////

    // Main.h

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\Menus.hpp>

    #include <vcl\ComCtrls.hpp>

    #include <vcl\DBGrids.hpp>

    #include <vcl\Grids.hpp>

    #include <vcl\DBCtrls.hpp>

    #include <vcl\ExtCtrls.hpp>

    #include "FindDirs2.h"

    class TForm1 : public TForm

    {

    __published:

      TMainMenu *MainMenu1;

      TMenuItem *File1;

      TMenuItem *Counter1;

      TMenuItem *N1;

      TMenuItem *Exit1;

      TMenuItem *Run1;

      TDBGrid *FileNamesGrid;

      TMenuItem *Options1;

      TMenuItem *Delete1;

      TMenuItem *DisableGrids1;

      TPanel *Panel2;

      TEdit *Edit1;

      TEdit *Edit2;

      TMenuItem *N2;

      TMenuItem *PickArchive1;

      TLabel *Label1;

      TLabel *Label2;

      TStatusBar *StatusBar1;

      TDBGrid *DirNamesGrid;

      TFindDirs *FindDirs1;

      void __fastcall Button1Click(TObject *Sender);

      void __fastcall Exit1Click(TObject *Sender);

      void __fastcall Delete1Click(TObject *Sender);

      void __fastcall PickArchive1Click(TObject *Sender);

      void __fastcall OnFoundDir(AnsiString NewDir);

      void __fastcall FindDirs1FoundFile(AnsiString NewDir);

    private:

      int FStartLevel;

      AnsiString FCurrentRoot;

      AnsiString FDiskName;

      void StartRun();

      void EndRun();

    public:

      virtual __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;

    #endif
Listing 24.2. The main form for the SearchDirs program.
    ///////////////////////////////////////

    // Main.cpp

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #include "DiskArchive.h"

    #include "FindDirsDMod.h"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    int GetLevel(AnsiString Source)

    {

      BOOL Done = False;

      int i = 0;

      char S[500];

      strcpy(S, Source.c_str());

      strtok(S, "\\");

      while (!Done)

      {

        if (strtok(NULL, "\\") == NULL)

          Done = True;

        else

          i += 1;

      }

      return i;

    }

    void TForm1::StartRun()

    {

      DirNamesGrid->DataSource = NULL;

      FileNamesGrid->DataSource = NULL;

      Screen->Cursor = (Controls::TCursor)crHourGlass;

      FindDirs1->StartString = Edit1->Text;

      FDiskName = Edit2->Text;

      FStartLevel = GetLevel(FindDirs1->StartDir);

      DMod->DiskNamesTable->Insert();

      DMod->DiskNamesTable->FieldByName("DiskName")->AsString = FDiskName;

      DMod->DiskNamesTable->Post();

    }

    void TForm1::EndRun()

    {

      Screen->Cursor = (Controls::TCursor)crDefault;

      DirNamesGrid->DataSource = DMod->DirNamesSource;

      FileNamesGrid->DataSource = DMod->FileNamesSource;

    }

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

      if (MessageBox((HWND)Handle, Edit1->Text.c_str(), "Make Run?", MB_YESNO) == ID_YES)

      {

        StartRun();

        FindDirs1->Run();

        EndRun();

      }

    }

    void __fastcall TForm1::Exit1Click(TObject *Sender)

    {

      Close();

    }

    void __fastcall TForm1::Delete1Click(TObject *Sender)

    {

      if (MessageBox((HWND)Handle, "Delete", "Delete Dialog", MB_YESNO) == ID_YES)

        DMod->CascadingDelete();

    }

    void __fastcall TForm1::PickArchive1Click(TObject *Sender)

    {

      ArchiveForm->ShowModal();

    }

    void __fastcall TForm1::OnFoundDir(AnsiString NewDir)

    {

      int i;

      StatusBar1->SimpleText = NewDir;

      StatusBar1->Update();

      FCurrentRoot = NewDir;

      i = GetLevel(FCurrentRoot);

      DMod->DirNamesTable->Insert();

      DMod->DirNamesTable->FieldByName("DirName")->AsString = NewDir;

      DMod->DirNamesTable->FieldByName("ALevel")->AsInteger = i;

      DMod->DirNamesTable->FieldByName("DiskCode")->AsInteger =

      DMod->DiskNamesTable->FieldByName("Code")->AsInteger;

      DMod->DirNamesTable->Post();

    }

    void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)

    {

      AnsiString Temp;

      if (FCurrentRoot.Length() == 0)

        Temp = FindDirs1->StartDir;

      else

        Temp = FCurrentRoot;

      DMod->FileNamesTable->Insert();

      DMod->FileNamesTable->FieldByName("Directory")->AsString = Temp;

      DMod->FileNamesTable->FieldByName("FileName")->AsString =

        ExtractFileName(NewDir);

      DMod->FileNamesTable->Post();

    }
Listing 24.3. The header for the programs data module.
    ///////////////////////////////////////

    // FindDirsDMod.h

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef FindDirsDModH

    #define FindDirsDModH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\DB.hpp>

    #include <vcl\DBTables.hpp>

    class TDMod : public TDataModule

    {

    __published:

      TDatabase *FileData1;

      TTable *DirNamesTable;

      TTable *FileNamesTable;

      TTable *DiskNamesTable;

      TTable *DiskTypeTable;

      TDataSource *DirNamesSource;

      TDataSource *FileNamesSource;

      TDataSource *DiskNamesSource;

      TDataSource *DiskTypeSource;

      TIntegerField *DirNamesTableALEVEL;

      TStringField *DirNamesTableDIRNAME;

      TIntegerField *DirNamesTableDISKCODE;

      TIntegerField *FileNamesTableCODE;

      TStringField *FileNamesTableDIRECTORY;

      TStringField *FileNamesTableFILENAME;

      TIntegerField *FileNamesTableDIRCODE;

      TIntegerField *DirNamesTableCODE;

      TQuery *DeleteFileNames;

      TQuery *DeleteDirNames;

      TQuery *DeleteDiskNames;

      TAutoIncField *DiskTypeTableCode;

      TStringField *DiskTypeTableDiskType;

      TIntegerField *DiskTypeTableDiskTypeCode;

      TAutoIncField *DiskNamesTableCode;

      TStringField *DiskNamesTableDiskName;

      TIntegerField *DiskNamesTableType;

      void __fastcall DModCreate(TObject *Sender);

    private:

    public:

      virtual __fastcall TDMod(TComponent* Owner);

      void CascadingDelete(void);

    };

    extern TDMod *DMod;

    #endif
Listing 24.4. The data module for the program.
    ///////////////////////////////////////

    // FindDirsDMod.cpp

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "FindDirsDMod.h"

    #pragma resource "*.dfm"

    TDMod *DMod;

    __fastcall TDMod::TDMod(TComponent* Owner)

            : TDataModule(Owner)

    {

    }

    void __fastcall TDMod::DModCreate(TObject *Sender)

    {

      DirNamesTable->Open();

      FileNamesTable->Open();

      DiskNamesTable->Open();

      DiskTypeTable->Open();

    }

    void TDMod::CascadingDelete(void)

    {

      DirNamesTable->First();

      DeleteFileNames->Prepare();

      while (!DirNamesTable->Eof)

      {

        DeleteFileNames->ExecSQL();

        DirNamesTable->Delete();

      }

      FileNamesTable->Refresh();

      DirNamesTable->Refresh();

      int i = DiskNamesTableCode->Value;

      DeleteDiskNames->Params->Items[0]->AsInteger = i;

      DeleteDiskNames->ExecSQL();

      DiskNamesTable->Refresh();

    }
### Listing 24.5. The header for the DiskArchive form.
    ///////////////////////////////////////

    // DiskArchive.h

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef DiskArchiveH

    #define DiskArchiveH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\DBGrids.hpp>

    #include <vcl\Grids.hpp>

    #include <vcl\ExtCtrls.hpp>

    #include <vcl\DBCtrls.hpp>

    #include <vcl\Buttons.hpp>

    class TArchiveForm : public TForm

    {

    __published:

      TDBGrid *DBGrid1;

      TPanel *Panel1;

      TDBNavigator *DBNavigator1;

      TBitBtn *BitBtn1;

    private:

    public:

      virtual __fastcall TArchiveForm(TComponent* Owner);

    };

    extern TArchiveForm *ArchiveForm;

    #endif
Listing 24.6. The DiskArchive form. This form is used only for displaying data. It has no custom code.
    ///////////////////////////////////////

    // DiskArchive.cpp

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "DiskArchive.h"

    #include "FindDirsDMod.h"

    #pragma resource "*.dfm"

    TArchiveForm *ArchiveForm;

    __fastcall TArchiveForm::TArchiveForm(TComponent* Owner)

        : TForm(Owner)

    {

    }
Listing 24.7. The header for the TFindDirs components.
    ///////////////////////////////////////

    // FindDirs2.h

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef FindDirs2H

    #define FindDirs2H

    #ifndef ComCtrlsHPP

    #include <vcl\ComCtrls.hpp>

    #endif

    struct TDirInfo

    {

      TSearchRec SearchRec;

      AnsiString CurDirectory;

    };

    class TDirStack : public TList

    {

    public:

      TDirStack(): TList() {};

      void Push(TDirInfo *DirInfo);

      TDirInfo *Pop();

    };

    typedef void __fastcall (__closure *TFoundDirEvent)(AnsiString NewDir);

    class TFindDirs : public TComponent

    {

    private:

      #ifdef DEBUG_FIND_DIRS

      FILE *fp;

      #endif

      AnsiString FStartString;     // Unchanged string passed in by user.

      AnsiString FFileExt;

      AnsiString FStartDir;        // The directory where the search starts

      AnsiString FCurDirectory;    // the current directory

      AnsiString FFileMask;        // The file mask of files to search for

      AnsiString FSearchString;    // Combine last three into a search string

      TDirStack *FDirStack;        // Stack of directories in the current dir

      TFoundDirEvent FOnFoundDir;

      TFoundDirEvent FOnFoundFile;

      void GetAllFiles(AnsiString *StartDir);

      void FoundAFile(TSearchRec *FileData);

      void FoundADir(TSearchRec *FileData);

      void __fastcall Initialize(void);

      void SetupSearchString();

      void GetNextDirectory();

      BOOL SetupFirstDirectory();

    protected:

      __fastcall virtual ~TFindDirs();

      virtual void ProcessFile(TSearchRec FileData, AnsiString FileName);

      virtual void ProcessDir(TSearchRec FileData, AnsiString DirName);

      virtual void __fastcall SetStartString(AnsiString AStartString);

    public:

      virtual __fastcall TFindDirs(TComponent *AOwner)

        : TComponent(AOwner) { FDirStack = NULL; FOnFoundDir = NULL; }

      virtual __fastcall TFindDirs(TComponent *AOwner, AnsiString AStartString);

      virtual void Run(void);

      __property AnsiString StartDir = {read = FStartDir};

      __property AnsiString CurDirectory = {read = FCurDirectory};

    __published:

      __property AnsiString StartString={read=FStartString, write=SetStartString};

      __property TFoundDirEvent OnFoundFile={read=FOnFoundFile, write=FOnFoundFile};

      __property TFoundDirEvent OnFoundDirf={read=FOnFoundDir, write=FOnFoundDir};

    };

    class TFindDirsList : public TFindDirs

    {

    private:

      TStringList *FFileList;

      TStringList *FDirList;

    protected:

      __fastcall virtual ~TFindDirsList();

      virtual void ProcessFile(TSearchRec FileData, AnsiString FileName);

      virtual void ProcessDir(TSearchRec FileData, AnsiString DirName);

    public:

      virtual __fastcall TFindDirsList(TComponent *AOwner): TFindDirs(AOwner) {}

      virtual __fastcall TFindDirsList(TComponent *AOwner, AnsiString AStartString);

    __published:

      __property TStringList *FileList = {read = FFileList, nodefault};

      __property TStringList *DirList = {read = FDirList, nodefault};

    };

    #endif
Listing 24.8. The main source file for the TFindDirs component.
    ///////////////////////////////////////

    // FindDirs2.cpp

    // SearchDirs

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #include <vcl\ComCtrls.hpp>

    #pragma hdrstop

    #include "FindDirs2.h"

    // -- TDirStack ----------------

    void TDirStack::Push(TDirInfo *DirInfo)

    {

      Add(DirInfo);

    }

    TDirInfo *TDirStack::Pop()

    {

      void *Temp = Items[0];

      Delete(0);

      return (TDirInfo *)Temp;

    }

    // -- TFindDirs ----------------

    __fastcall TFindDirs::TFindDirs(TComponent *AOwner, AnsiString AStartString)

    : TComponent(AOwner)

    {

      SetStartString(AStartString); // Don't set data store directly!

      FDirStack = NULL;

      FOnFoundDir = NULL;

    }

    void __fastcall TFindDirs::SetStartString(AnsiString AStartString)

    {

      FStartString = AStartString;

      FStartDir = ExtractFilePath(FStartString);

      FFileExt = ExtractFileExt(FStartString);

    }

    void __fastcall TFindDirs::Initialize(void)

    {

      #ifdef DEBUG_FIND_DIRS

      if ((fp = fopen("c:\searchdirs.txt", "w+")) == NULL)

      {

        ShowMessage("Can't open debug file");

      }

      #endif

      if (FDirStack)

        delete FDirStack;

      FDirStack = new TDirStack;

      FCurDirectory = "";

      FFileMask = "*.*";

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "%s %s %s \n", FStartDir.c_str(), FFileMask.c_str(), FFileExt.c_str());

      #endif

    }

    __fastcall TFindDirs::~TFindDirs()

    {

      #ifdef DEBUG_FIND_DIRS

      fclose(fp);

      #endif

    }

    void TFindDirs::ProcessFile(TSearchRec FileData, AnsiString FileName)

    {

      if (FOnFoundFile != NULL)

        FOnFoundFile(FileName);

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "File found: %s\n", FileName);

      #endif

    }

    void TFindDirs::ProcessDir(TSearchRec FileData, AnsiString DirName)

    {

      if (FOnFoundDir != NULL)

        FOnFoundDir(DirName);

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "Dir found: %s\n", DirName);

      #endif

    }

    void TFindDirs::FoundADir(TSearchRec *FileData)

    {

      AnsiString FullName;

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "Dir found: %s\n", FileData->Name);

      #endif

      if ((FileData->Name != ".") &&

         (FileData->Name != ".."))

      {

        TDirInfo *DirInfo = new TDirInfo;

        DirInfo->CurDirectory = AnsiString(FCurDirectory + FileData->Name + "\\");

        DirInfo->SearchRec = *FileData;

        #ifdef DEBUG_FIND_DIRS

        fprintf(fp, "DirInfo: %s\n", DirInfo->SearchRec.Name);

        fflush(fp);

        #endif

        FDirStack->Push(DirInfo);

      }

    }

    ///////////////////////////////////////

    // FoundAFile

    ///////////////////////////////////////

    void TFindDirs::FoundAFile(TSearchRec *FileData)

    {

      AnsiString FullName;

      if ((FFileExt == ".*") ||

          (UpperCase(ExtractFileExt(FileData->Name)) == UpperCase(FFileExt)))

      {

        FullName = FStartDir + FCurDirectory + FileData->Name;

        ProcessFile(*FileData, FullName);

      }

    }

    ///////////////////////////////////////

    // GetAllFiles

    ///////////////////////////////////////

    void TFindDirs::GetAllFiles(AnsiString *StartDir)

    {

      TSearchRec FileData;

      int Info;

      Info = FindFirst(StartDir->c_str(), faDirectory, FileData);

      while (Info == 0)

      {

        if (FileData.Attr == faDirectory)

          FoundADir(&FileData);

        else

          FoundAFile(&FileData);

        Info = FindNext(FileData);

      }

      FindClose(&FileData.FindData);

    }

    ///////////////////////////////////////

    // SetupSearchString

    ///////////////////////////////////////

    void TFindDirs::SetupSearchString()

    {

      FSearchString = FStartDir + FCurDirectory + FFileMask;

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "FSearchString: %s \n", FSearchString);

      #endif

    }

    ///////////////////////////////////////

    // GetNextDirectory

    ///////////////////////////////////////

    void TFindDirs::GetNextDirectory()

    {

      TDirInfo *FDirInfo = FDirStack->Pop();

      FCurDirectory = FDirInfo->CurDirectory;

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "Next Directory: %s\n", FCurDirectory);

      fflush(fp);

      #endif

      ProcessDir(FDirInfo->SearchRec, FStartDir + FCurDirectory);

      delete FDirInfo;

    }

    BOOL TFindDirs::SetupFirstDirectory()

    {

      TSearchRec FileData;

      AnsiString SearchStr = FStartDir + FFileMask;

      int Info = FindFirst(SearchStr.c_str(), faDirectory, FileData);

      FindClose(&FileData.FindData);

      if (Info == 0)

      {

        TDirInfo *DirInfo = new TDirInfo;

        DirInfo->CurDirectory = FCurDirectory;

        FileData.Name = FStartDir;

        DirInfo->SearchRec = FileData;

        FDirStack->Push(DirInfo);

        return TRUE;

      }

      else

        return FALSE;

    }

    ///////////////////////////////////////

    // Run: FindFilesAndDirs

    ///////////////////////////////////////

    void TFindDirs::Run(void)

    {

      BOOL FDone = False;

      BOOL FirstTime = TRUE;

      Initialize();

      if (!SetupFirstDirectory())

      {

        ShowMessage("Invalid Search String");

        return;

      }

      while (!FDone)

      {

        SetupSearchString();

        if (!FirstTime)

          GetAllFiles(&FSearchString);

        if (FDirStack->Count > 0)

          GetNextDirectory();

        else

          FDone = True;

        FirstTime = FALSE;

      }

      FDirStack->Free();

      FDirStack = NULL;

    }

    ///////////////////////////////////////

    // TFindDirsList //////////////////////

    ///////////////////////////////////////

    __fastcall TFindDirsList::TFindDirsList(TComponent *AOwner,

      AnsiString AStartDir): TFindDirs(AOwner, AStartDir)

    {

      FFileList = new TStringList;

      FFileList->Sorted = True;

      FDirList = new TStringList;

      FDirList->Sorted = True;

    }

    __fastcall TFindDirsList::~TFindDirsList()

    {

      FFileList->Free();

      FDirList->Free();

    }

    void TFindDirsList::ProcessFile(TSearchRec FileData, AnsiString FileName)

    {

      FFileList->Add(FileName);

    }

    void TFindDirsList::ProcessDir(TSearchRec FileData, AnsiString DirName)

    {

      FDirList->Add(DirName);

    }

    namespace Finddirs2

    {

      void __fastcall Register()

      {

        TComponentClass classes[2] = {__classid(TFindDirs),

          __classid(TFindDirsList)};

        RegisterComponents("Unleash", classes, 1);

      }

    }
The database aspects of this program are important. You will find the files used by the program in the `Data` directory on the CD that ships with this book. As I explain in the readme file that accompanies the CD, you should set up an alias called `CUnleashed` that points to these files. Needless to say, you should recreate the data directory on your hard drive, and should not use the read-only directory found on the CD, but should make sure they've been copied onto your hard drive. The `DatabaseName` for the `TDatabase` object used in my version of the program contains the string `FileData`, so you might get an error about that alias if you try to run the program. However, you do not want to try to fix the `FileData` alias, rather the one called `CUnleashed`. The data module for the program is shown in Figure 24.2. To use the program, first point it to a subdirectory on your hard disk. Then type in a file mask in the edit control at the top of the form. For example, you might type in `c:\temp\*.cpp` or simply `c:\temp\*.*`. Be sure to type in the file mask. It would cause an error if you typed `I:\` instead of `I:\*.*`. (In general, the program is not robust enough to check for many user errors.) When you click the button at the bottom of the program, the code iterates through all the directories beneath the one you pointed to and finds all the files that have the extension you designated. The program then places these files in a list database. FIGURE 24.2. The data module for the SearchDirs program. NOTE: I tested the `TFindDirs` component fairly extensively. For instance, I aimed it at the root of my C drive, which contains 1.12GB of space, with all but about 100MB used. The program ran fine against the thousands of files on that drive. I also aimed the component at nested directories containing long filenames, and again it handled the challenge without incident. One problem I have had with the component involves sharing violations. If the SearchDirs program is iterating through a series of files, and one of which is locked by the file system, then the `TFindDirs` component will probably raise a rather unsightly access violation. Check the CD and my Web site to see if I have come up with a fix for this problem. In the meantime, you should make sure other programs are closed before running the SearchDirs program, or be sure to aim the program at drives that do not have open files on them. Note that running the SearchDirs program against huge drives will create truly monolithic Paradox files containing lists of the files found during the search. For instance, after running against my C drive, the main DB file and its index were both more than 9MB in size. I have not tested the program to see what would happen if I ran out of disk space for the DB file during a run of the program. To use the `FindDirs` component, you need do nothing more than assign it a string containing the file mask you want to use. You can do so via a property in the Object Inspector; you can insert the information at runtime:
      if (MessageBox((HWND)Handle, Edit1->Text.c_str(), "Make Run?", MB_YESNO) == ID_YES)

      {

        FindDirs1->StartString = Edit1->Text;

        FindDirs1->Run();

      }
That's all you have to do to use the component, other than respond to events when files or directories are found. You can set up these event handlers automatically, as described in the next section. ### Iterating Through Directories with TFindDirs The SearchDirs program uses the `TFindDirs` component to iterate through directories. The `TFindDirs` component sends events to your program whenever a new directory or a new file is found. The events include the name of the new directory or file, as well as information about the size and date of the files the component finds. You can respond to these events in any way you want. For example, this program stores the names in a Paradox file. NOTE: The SearchDirs program tends to create huge database files fairly quickly. The main problem here is that I need to store long filenames, which means I need to set aside large fields in the table. This problem is severe enough that I am going to eventually need to come up with some kind of custom solution to storing these strings. For now, however, I am just living with some very big DB files on my hard drive. One possible solution to this problem would be to save all the information from a directory in a `TStringList` and then save the `TStringList` as a single string, which is one of the capabilities of this object. I could then save the whole string in a single blob field, which would make a better use of space. When I want to display the directory to a user, I could ask the `TStringList` to read the string in again and store it in a `TMemoryStream`. The SearchDirs program responds as follows when a file with the proper extension is found:
    void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)

    {

      AnsiString Temp;

      if (FCurrentRoot.Length() == 0)

        Temp = FindDirs1->StartDir;

      else

        Temp = FCurrentRoot;

      DMod->FileNamesTable->Insert();

      DMod->FileNamesTable->FieldByName("Directory")->AsString = Temp;

      DMod->FileNamesTable->FieldByName("FileName")->AsString =

        ExtractFileName(NewDir);

      DMod->FileNamesTable->Post();

    }
As you can see, the code does nothing more than shove the filename in a table. To set up this method, you merely have to click the `TFindDirs` `OnFoundFile` event in the Object Inspector. The `OnFoundFile` and `OnFoundDir` events of `TFindDirs` are of type `TFoundDirEvent`:
    typedef void __fastcall (__closure *TFoundDirEvent)(AnsiString NewDir);
I created two private events for `TFindDirs` that are of this type:
    TFoundDirEvent FOnFoundDir;
    TFoundDirEvent FOnFoundFile;
I then make these events into published properties that can be seen in the Object Inspector:
    __property TFoundDirEvent OnFoundFile={read=FOnFoundFile, write=FOnFoundFile};

    __property TFoundDirEvent OnFoundDirf={read=FOnFoundDir, write=FOnFoundDir};
If you're unclear about what is happening here, study the code in `FindDirs2.h`, or turn to the section about creating and using events covered in depth in Chapter 4, "Events." If you have events like this one set up, then you need merely to click them in the Object Inspector, and the outline for your code will be created for you automatically. The following, for example, is the code BCB will produce if you click the `OnFoundDir` event:
    void __fastcall TForm1::FindDirs1FoundFile(AnsiString NewDir)

    {

    }
The great thing about events is that they not only save you time when you're typing, but they also help to show how to use the component. After you see a method like `FindDirs1FoundFile`, you don't have to worry about going to the online help to find out how to get the directories found by the component! What you're supposed to do is obvious. The following code in `FoundDirs2.cpp` calls the event handlers:
    void TFindDirs::ProcessDir(TSearchRec FileData, AnsiString DirName)

    {

      if (FOnFoundDir != NULL)

        FOnFoundDir(DirName);

      #ifdef DEBUG_FIND_DIRS

      fprintf(fp, "Dir found: %s\n", DirName);

      #endif

    }
This code checks to see whether the `FOnFoundDir` event is set to `NULL`. If it is not, the event is called. NOTE: Notice that the code for the `ProcessDir` method uses conditional compilation to leave you the option of writing debug output to a file. I used this code when I was creating the unit. My goal was to find a way to write out a list of the directories and files found during a run of the program. I could then study the list to make sure my algorithm was working correctly. #### Layering Your Objects `TFindDirs` has a descendant object called `TFindDirsList`. Part of the built-in functionality of the `TFindDirsList` unit is to maintain lists of the files it finds. After you finish searching all the directories, the list is ready for you to do with as you want. This list is kept in a `TStringList` object, so you can just assign it to the `Items` property in a list box, as shown in this code excerpt:
    ListBox1->Items = FileIterator1->FileList;
This idea of layering your components so that you can create different objects, descending from different parents, under different circumstances, is key to object-oriented design. You don't want to push too much functionality up too high in the object hierarchy; otherwise, you will be forced to rewrite the object to get access to a subset of its functionality. For example, if the BCB developers had not created a `TDataSet` component but had instead created one component called `TTable`, they would have had to duplicate that same functionality in the `TQuery` component. This approach is wasteful. The smart thing to do is to build a component called `TDataSet` and end its functionality at the point at which the specific attributes of `TQuery` and `TTable` need to be defined. That way, `TQuery` and `TTable` can both reuse the functionality of `TDataSet` rather than have to rewrite that same functionality for both objects. Before I close this section, let me reiterate some key points. The `TFindDirs` object is the brains of this particular operation. It knows how to iterate through directories, how to find all the files in each directory, and how to notify the user when new directories or files are found. The SearchDirs program is just a wrapper around this core functionality. ### Iterating Through Directories The task of iterating through directories has a simple recursive solution. However, recursion is a slow and time-consuming technique that is also wasteful of stack space. As a result, `TFindDirs` creates its own stacks and pushes the directories it finds onto them. NOTE: BCB includes some built-in tools for creating stacks and lists. For example, the `TList` and `TStringList` objects are available. I use these tools here because they are simple objects specific to the VCL. Another alternative would have been to use the STL. You can find the following objects in `FindDirs2.h`: `TDirInfo`: This simple structure keeps track of the current directory and of the complete set of information describing a particular file. `TDirStack`: I need a place to push each directory after I find it. That leaves me free to iterate through all the files in the directory first and then go back and pop each subdirectory off the stack when I am free to examine it. `TFindDirs`: This object provides the ability to iterate through directories. `TFindDirsList`: This object adds `TStringList` objects to `TFindDirs`. These objects are accessible as properties, and they are maintained automatically by the object. I do not use the `TFindDirsList` object in the `SearchDirs` example. However, you'll find it very helpful when you're experimenting with these objects on your own. To dust off the classic analogy used in these situations, the stacks implemented here are like piles of plates in a kitchen cabinet. You can put one plate down and then add another one to it. When you need one of the plates, you take the first one off either the top or the bottom, depending on whether it's a FIFO or a LIFO stack. Putting a new plate on the top of a stack is called pushing the object onto the stack, and removing a plate is called popping the object off the stack. For more information on stacks, refer to any book on basic programming theory. Books that cover the STL (Standard Template Library) in depth also usually cover this subject in the process. Look at the implementation of the following FIFO stack:
    void TDirStack::Push(TDirInfo *DirInfo)

    {

      Add(DirInfo);

    }

    TDirInfo *TDirStack::Pop()

    {

      void *Temp = Items[0];

      Delete(0);

      return (TDirInfo *)Temp;

    }
The code is so simple because it is built on top of the `TList` object that is part of the VCL:
    class TDirStack : public TList

    {

    public:

      TDirStack(): TList() {};

      void Push(TDirInfo *DirInfo);

      TDirInfo *Pop();

    };
One look at this simple code, and you can see why I was drawn to the `TList` object rather than the STL. If I had had any doubt in my mind, then, of course, I would have turned to the VCL, because this book is about BCB, not about the Standard Template Library. ### Using `FindFirst`, `FindNext`, and `FindClose` In this section, I continue the examination of the stacks created in the `TFindDirs` units. The cores of these stacks are the calls to `FindFirst`, `FindNext`, and `FindClose` that search through directories looking for particular files. Using `FindFirst`, `FindNext`, and `FindClose` is like typing `DIR` in a directory at the DOS prompt. `FindFirst` finds the first file in the directory, and `FindNext` finds the remaining files. You should call `FindClose` when you're finished with the process. `FindFirst` and the others are found in the `SysUtils` unit. These calls enable you to specify a directory and file mask, as if you were issuing a command of the following type at the DOS prompt:
    dir c:\aut*.bat
This command would, of course, show all files beginning with `aut` and ending in `.bat`. This particular command would typically find `AUTOEXEC.BAT` and perhaps one or two other files. When you call `FindFirst`, you pass in three parameters:
    extern int __fastcall FindFirst(const System::AnsiString Path,

                                    int Attr, TSearchRec &F);
The first parameter contains the path and file mask that specify the files you want to find. For example, you might pass in `"c:\\BCB\\include\\vcl\\*.hpp"` in this parameter. The second parameter lists the type of files you want to see:
    faReadOnly     0x01     Read-only files

    faHidden       0x02     Hidden files

    faSysFile      0x04     System files

    faVolumeID     0x08     Volume ID files

    faDirectory    0x10     Directory files

    faArchive      0x20     Archive files

    faAnyFile      0x3F     Any file
Most of the time, you should pass in `faArchive` in this parameter. However, if you want to see directories, pass in `faDirectory`. The `Attribute` parameter is not a filter. No matter what flags you use, you will always get all normal files in the directory. Passing `faDirectory` causes directories to be included in the list of normal files; it does not limit the list to directories. You can use `OR` to concatenate several different `fa`XXX constants, if you want. The final parameter is a variable of type `TSearchRec`, which is declared as follows:
    struct TSearchRec {

      int Time;

      int Size;

      int Attr;

      System::AnsiString Name;

      int ExcludeAttr;

      int FindHandle;

      WIN32_FIND_DATAA FindData; };
The most important value in `TSearchRec` is the `Name` field, which on success specifies the name of the file found. `FindFirst` returns zero if it finds a file and nonzero if the call fails. However, I rely heavily on the `FindData` portion of the record. `FindData` is the original structure passed back from the operating system. The rest of the fields are derived from it and are presented here in this form so as to present a simple, easy-to-use interface to VCL programmers. `WIN32_FIND_DATA` looks like this:
    typedef struct _WIN32_FIND_DATA { // wfd

        DWORD dwFileAttributes;

        FILETIME ftCreationTime;

        FILETIME ftLastAccessTime;

        FILETIME ftLastWriteTime;

        DWORD    nFileSizeHigh;

        DWORD    nFileSizeLow;

        DWORD    dwReserved0;

        DWORD    dwReserved1;

        TCHAR    cFileName[ MAX_PATH ];

        TCHAR    cAlternateFileName[ 14 ];

    } WIN32_FIND_DATA;
`FindNext` works exactly like `FindFirst`, except that you have to pass in only a variable of type `TSearchRec` because it is assumed that the mask and file attribute are the same. Once again, `FindNext` returns zero if all goes well, and a nonzero value if it can't find a file. You should call `FindClose` after completing a `FindFirst`/`FindNext` sequence. Given this information, here is a simple way to call `FindFirst`, `FindNext`, and `FindClose`:
    void TFindDirs::GetAllFiles(AnsiString *StartDir)

    {

      TSearchRec FileData;

      int Info;

      Info = FindFirst(StartDir->c_str(), faDirectory, FileData);

      while (Info == 0)

      {

        if (FileData.Attr == faDirectory)

          FoundADir(&FileData);

        else

          FoundAFile(&FileData);

        Info = FindNext(FileData);

      }

      FindClose(&FileData.FindData);

    }
That's all I'm going to say about the basic structure of the `TFindDirs` object. As I said earlier, you can learn more about stacks by studying a book on basic programming data structures. This book, however, is about BCB, so I'm going to move on to a discussion of creating event handlers. ### Summary The SearchDirs program, along with the `TFindDirs` component, points the way toward an understanding of BCB's greatest strengths. `TFindDirs` is not a particularly difficult piece of code, but it is sufficiently complex to highlight the fact that you can place almost any kind of logic inside a BCB object. If you want to write multimedia code, or code that enables conversations on a network or simulates the behavior of a submarine, you can write a BCB component or set of components that will encapsulate the logic needed to reach your goal. More importantly, these components can then be placed on the Component Palette and dropped onto a form where they can easily be manipulated through the Object Inspector. Objects help you hide complexity and help you reuse code. The Object Inspector--and its related property editors and component editors--provide an elegant, easy-to-use interface to any object. Component architectures represent one of the most important tools in programming today, and BCB has by far the best implementation of a component architecture currently available in the C++ market. In fact, the VCL is several orders of magnitude better at creating components than any other existing Windows-based technology.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.